#ifndef __MATRIX__H__
#define __MATRIX__H__
#include "vector.cpp"
#include <iostream>
#include <string.h>

template <class T> class Matrix{
	private:
		unsigned int numRow;
		unsigned int numCol;
		T** tab;
	public:
		Matrix();
		Matrix(unsigned int numRow_,unsigned int numCol_);
		Matrix(unsigned int numRow_,unsigned int numCol_, T *t);
		Matrix(const Matrix &m);
		static Matrix MatrixIdent(unsigned int numRow_,unsigned int numCol_);
		static Matrix MatrixDiag(unsigned int numRow_,unsigned int numCol_);
		static Matrix MatrixRotateEuler(vector<T> v);
		static Matrix MatrixRotateX(double x);
		static Matrix MatrixRotateY(double y);
		static Matrix MatrixRotateZ(double z);
		static Matrix MatrixScale(vector<T> v);
		~Matrix();
		void fill(T *t);
		void fillCol(T *t);
		Matrix<T>& operator=(const Matrix &m);
		Matrix<T> operator*(const Matrix &m) const;
		void operator*=(const int &t);
		void operator*=(const Matrix &m);
		T* operator[](unsigned int row) const;
		const unsigned int& getNumCol() const;
		const unsigned int& getNumRow() const;
		void print() const;
};

template <class T>
Matrix<T>::Matrix(){
	numRow=0;
	numCol=0;
	tab = NULL;
}

template <class T>
Matrix<T>::Matrix(unsigned int numRow_,unsigned int numCol_){
	numRow=numRow_;
	numCol=numCol_;
	
	tab = new T*[numRow];
	
	for(unsigned int i=0;i<numRow;i++){
		tab[i] = new T[numCol];
		for(unsigned int j=0;j<numCol;j++){
			tab[i][j] = 0;
		}
	}
}

//constructeur
template <class T>
Matrix<T>::Matrix(unsigned int numRow_,unsigned int numCol_, T *t){
	unsigned int k = 0;

	numRow=numRow_;
	numCol=numCol_;
	
	tab = new T*[numRow];

	for(unsigned int i=0;i<numRow;i++){
		tab[i] = new T[numCol];
		for(unsigned int j=0;j<numCol;j++){
			tab[i][j]=T(t[k]);
			k++;
		}
	}
}

//Cnstructeur de copie
template <class T>
Matrix<T>::Matrix(const Matrix &m){
	(*this) = m;
}

template <class T> 
Matrix<T> Matrix<T>::MatrixIdent(unsigned int numRow_,unsigned int numCol_){
	Matrix<T> m = Matrix(numRow_,numCol_,0);
	return m;
}

template <class T> 
Matrix<T> Matrix<T>::MatrixDiag(unsigned int numRow_,unsigned int numCol_){
	Matrix<T> m = Matrix(numRow_,numCol_,0);
	
	for(unsigned int i=0;i<m.numRow && i<m.numCol;i++){
		m.tab[i][i] = 1;
	}
	return m;
}

template <class T>
Matrix<T> Matrix<T>::MatrixRotateEuler(vector<T> v){
	T A,B,C,D,E,F,AD,BD;

	A = (T)cos(v.x);
	B = (T)sin(v.x);
	C = (T)cos(v.y);
	D = (T)sin(v.y);
	E = (T)cos(v.z);
	F = (T)sin(v.z);

	AD = A*D;
	BD = B*D;
	

	T t[4*4] = {C*E, -C*F, -D, T(0.), -BD*E+A*F, -BD*F+A*E, -B*C, T(0.),
				AD*E+B*F, -AD*F+B*E, A*C, T(0.), T(0.), T(0.), T(0.), T(1.)};

	Matrix<T> m(4,4,t);
	m.print();
	return m;
}


template <class T>
Matrix<T> Matrix<T>::MatrixRotateX(double x){
	Matrix<T> m(4,4);
	double c,s;
	c = cos(x);
	s = sin(x);

	m[0][0] = 1;
	m[3][3] = 1;

	m[1][1] = c;
	m[1][2] = -s;
	m[2][1] = s;
	m[2][2] = c;

	m.print();
	return m;
}

template <class T>
Matrix<T> Matrix<T>::MatrixRotateY(double y){
	Matrix<T> m(4,4);
	double c,s;
	c = cos(y);
	s = sin(y);

	m[0][0] = c;
	m[0][2] = -s;

	m[1][1] = 1;

	m[2][0] = s;
	m[2][2] = c;

	m[3][3] = 1;

	m.print();
	return m;
}

template <class T>
Matrix<T> Matrix<T>::MatrixRotateZ(double z){
	Matrix<T> m(4,4);
	double c,s;
	c = cos(z);
	s = sin(z);

	m[0][0] = c;
	m[0][1] = -s;

	m[1][0] = s;
	m[1][1] = c;

	m[2][2] = 1;

	m[3][3] = 1;

	m.print();
	return m;
}

template <class T>
Matrix<T> Matrix<T>::MatrixScale(vector<T> v){
	Matrix<T> m(4,4);
	
	m[0][0]= v.x;
	m[1][1]= v.y;
	m[2][2]= v.z;
	m[3][3]= 1;

	return m;
}

template <class T>
Matrix<T>::~Matrix(){
	if(tab){
		for(unsigned int i=0;i<numRow;i++){
			delete[] tab[i];
		}
		delete[] tab;
	}
}

template <class T>
void Matrix<T>::fill( T *t ){
	unsigned int k = 0;
	for(unsigned int i=0;i<numRow;i++){
		for(unsigned int j=0;j<numCol;j++){
			tab[i][j]=t[k];k++;
		}
	}
}

template <class T>
void Matrix<T>::fillCol( T *t ){
	unsigned int k = 0;
	for(unsigned int i=0;i<numCol;i++){
		for(unsigned int j=0;j<numRow;j++){
			tab[j][i]=t[k];k++;
		}
	}
}
template <class T>
const unsigned int& Matrix<T>::getNumCol() const{
	return numCol;
}

template <class T>
const unsigned int& Matrix<T>::getNumRow() const{
	return numRow;
}

template <class T>
Matrix<T>& Matrix<T>::operator=(const Matrix &m){
	unsigned int i,j;

	if(!m.tab){
		if(tab){
			for(i=0;i<numRow;i++){			//Désalloc dim2
				delete[] tab[i];
			}
			delete[] tab;
			numRow = 0;
			numCol = 0;
		}
		return *this;
	}

	//Modification de la structure du tableau
	if(tab){
		if(m.numRow != numRow || numCol != m.numCol ){	//Si dimension différente
			for(i=0;i<numRow;i++){			//Désalloc dim2
				delete[] tab[i];
			}
			if(m.numRow != numRow){						//Si besoin, désalloc dim1
				delete[] tab;
				tab = new T*[m.numRow];
			}
			for(i=0;i<m.numRow;i++){		//Réalloc colonne
				tab[i] = new T[m.numCol];
			}			
		}
	}
	else{
		tab = new T*[m.numRow];
		for(i=0;i<m.numRow;i++){
			tab[i] = new T[m.numCol];
		}
	}

	//Changement taille
	numRow = m.numRow;
	numCol = m.numCol;

	//Remplissage
	for(i=0;i<numRow;i++){
		for(j=0;j<numCol;j++){
			tab[i][j] = m.tab[i][j];
		}
	}

	return *this;
}

template <class T>
Matrix<T> Matrix<T>::operator*(const Matrix &m) const{
	/*
		A = this
		B = m
a*b
	*/
	Matrix r = Matrix(m.numRow,m.numCol);
	for(unsigned int i=0;i<numRow;i++){
		for(unsigned int j=0;j<m.numCol;j++){
			r[i][j]=0;
			for(unsigned int k=0;k<m.numRow;k++){
				r[i][j] += tab[i][k] * m.tab[k][j];
			}
		}
	}
	return r;
}

template <class T>
void Matrix<T>::operator*=(const int &t){
	for(unsigned int i=0;i<numRow;i++){
		for(unsigned int j=0;j<numCol;j++){
			tab[i][j] = tab[i][j]*t;	
		}
	}
}

template <class T>
void Matrix<T>::operator*=(const Matrix &m){
	T x;
	for(unsigned int i=0;i<m.numRow;i++){
		for(unsigned int j=0;j<numCol;j++){
			x = 0;
			for(unsigned int k=0;k<numRow;k++){
				x += m.tab[i][k] * tab[k][j];
			}
			tab[i][j] = x;	
		}
	}
}

template <class T>
T* Matrix<T>::operator[](unsigned int row) const{
	if(row>numRow){ 
		return NULL;
	}
	return tab[row];
}

template <class T>
void Matrix<T>::print() const{
	if(tab){
		for(unsigned int i=0;i<numRow;i++){
			for(unsigned int j=0;j<numCol;j++){
				std::cout<<" "<<tab[i][j];
			}
			std::cout<<std::endl;
		}
	}
}



Matrix  Matrix::translation(double x,double y,double z)
{
	Matrix rep(4,4);
	
	
	rep.fill(	1.,		0.,		0.,		x,
			0.,		1.,		0.,		y,
			0.,		0.,		1.,		z,
			0.,		0.,		0.,		1.);
			
	return rep;	
}

Matrix  Matrix::projection(double z)
{
	Matrix rep(4,4);
	
	
	rep.fill(	1.,		0.,		0.,		0.,
			0.,		1.,		0.,		0.,
			0.,		0.,		0.,		0.,
			0.,		0.,		-1./z,		1.);
			
	return rep;	
}

//affichage de la matrice
std::ostream& operator<<(std::ostream& s, Matrix& m) {
	for (int i=0; i<m.getNumRow(); i++)  {
		for (int j=0; j<m.getNumCol(); j++) {
			s << m[i][j];
			if (j<m.getNumCol()-1) s << "\t";
		}
		s << std::endl;
	}
	return s;
}





#endif
